﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

using Microsoft.Practices.EnterpriseLibrary.Logging;

namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Utility/facade class which wraps common Ms Logging Application Block functionalities.
    /// </summary>
    /// <remarks>All logged entries can be asynchronously written in separate threads, meaning that logging
    /// doesn't slow down operations which are being logged.</remarks>
    public static class Log
    {
        #region Settings.

        // Priority used for app-level messages.
        const int highPriority = 10;
        // Priority used for transaction-level messages.
        const int lowPriority = 1;

        #endregion

        #region Asynchronously write log entry in a separate thread.

        static bool asyncMode = true;

        /// <summary>
        /// Gets or sets whether log entries are written asynchronously in a seperate thread.
        /// </summary>
        /// <value><b>true</b> if asynchronous mode is enabled, <b>false</b> otherwise. Default is <b>true</b>.</value>
        /// <remarks>It can be usefull to temporary set this property to false while initially configuring and
        /// testing the <b>loggingConfiguration</b> section of a .config file. Once logging configuration
        /// is properly configured, set this property to <b>true</b> to improve performance.</remarks>
        public static bool AsyncMode
        {
            get { return asyncMode; }
            set { asyncMode = value; }
        }

        /// <summary>
        /// Writes log entry.
        /// </summary>
        /// <param name="entry">Entry to write to log.</param>
        /// <remarks>The log entry is written asynchronously in a seperate thread if <see cref="AsyncMode"/> property is set to <b>true</b>.</remarks>
        private static void WriteLogEntry(LogEntry entry)
        {
            entry.TimeStamp = Time.Now;
            WriteLogEntry(entry, asyncMode);
        }

        /// <summary>
        /// Writes log entry.
        /// </summary>
        /// <param name="entry">Entry to write to log.</param>
        /// <param name="asynchronously">Specifies whether the log entry is written asynchronously in a seperate thread.</param>
        private static void WriteLogEntry(LogEntry entry, bool asynchronously)
        {
            if (asynchronously)
            {
                ThreadPool.QueueUserWorkItem(AsyncWrite, entry);
            }
            else
            {
                Logger.Write(entry);
            }
        }

        private static void AsyncWrite(object logEntry)
        {
            Logger.Write((LogEntry)logEntry);
        }

        #endregion

        // System/app-level events.

        #region AppError.

        /// <summary>
        /// Logs high priority application error/exception that breaks the current operation/transaction but doesn't terminate the entire application.
        /// </summary>
        /// <param name="error">Error to log.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Error"/> severity.</para>
        /// <para>Use this method to log errors that are the result of logical development errors, such as unhandled 
        /// exceptions, invalid operation or program state etc. These are errors that typically require a bug fix.</para>
        /// <para>Recommendation: messages of this type should be logged in event log and application's errorlog file.</para>
        /// </remarks>
        public static void AppError(Exception error)
        {
            if (error != null)
                AppError(error.GetType().Name, error.ToString(), null);
            else
                AppError(null, null, null);
        }

        /// <summary>
        /// Logs high priority application error/exception that breaks the current operation/transaction but doesn't terminate the entire application.
        /// </summary>
        /// <param name="errorName">Error name/identifier.</param>
        /// <param name="errorDetails">Detailed message body to log.</param>
        /// <param name="properties">Optional dictionary of key/value pairs to record.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Error"/> severity.</para>
        /// <para>Use this method to log errors that are the result of logical development errors, such as unhandled 
        /// exceptions, invalid operation or program state etc. These are errors that typically require a bug fix.</para>
        /// <para>Recommendation: messages of this type should be logged in event log and application's errorlog file.</para>
        /// </remarks>
        public static void AppError(string errorName, string errorDetails, IDictionary<string, object> properties)
        {
            LogEntry entry = new LogEntry();
            entry.Severity = TraceEventType.Error;
            entry.Categories.Add("AppError");
            entry.Priority = highPriority;

            entry.Title = (errorName != null) ? errorName : "";
            entry.Message = (errorDetails != null) ? errorDetails : "";
            if (properties != null)
                entry.ExtendedProperties = properties;

            WriteLogEntry(entry);
        }

        #endregion

        #region CriticalAppError.

        /// <summary>
        /// Logs high priority, critical application error/exception that breaks a set or all application functionalities.
        /// </summary>
        /// <param name="error">Error to log.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Critical"/> severity.</para>
        /// <para>Use this method to log critical system failures, such as network or database server problems.
        /// These are errors that typically require immediate attention of system administrators or developers.</para>
        /// <para>Recommendation: messages of this type should be logged in event log and application's errorlog file.
        /// Information about them should be immediately mailed to system administrators and developers.</para></remarks>
        public static void CriticalAppError(Exception error)
        {
            if (error != null)
                CriticalAppError(error.GetType().Name, error.ToString(), null);
            else
                CriticalAppError(null, null, null);
        }

        /// <summary>
        /// Logs high priority, critical application error/exception that breaks a set or all application functionalities.
        /// </summary>
        /// <param name="errorName">Error name/identifier.</param>
        /// <param name="errorDetails">Detailed message body to log.</param>
        /// <param name="properties">Optional dictionary of key/value pairs to record.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Critical"/> severity.</para>
        /// <para>Use this method to log critical system failures, such as network or database server problems.
        /// These are errors that typically require immediate attention of system administrators or developers.</para>
        /// <para>Recommendation: messages of this type should be logged in event log and application's errorlog file.
        /// Information about them should be immediately mailed to system administrators and developers.</para></remarks>
        public static void CriticalAppError(string errorName, string errorDetails, IDictionary<string, object> properties)
        {
            LogEntry entry = new LogEntry();
            entry.Severity = TraceEventType.Critical;
            entry.Categories.Add("CriticalAppError");
            entry.Priority = highPriority;

            entry.Title = (errorName != null) ? errorName : "";
            entry.Message = (errorDetails != null) ? errorDetails : "";
            if (properties != null)
                entry.ExtendedProperties = properties;

            WriteLogEntry(entry);
        }

        #endregion

        #region AppWarning.

        /// <summary>
        /// Logs high priority, noncritical problem that needs to be adressed to avoid possible future issues.
        /// </summary>
        /// <param name="warningName">Warning name/identifier.</param>
        /// <param name="warningDetails">Detailed message body to log.</param>
        /// <param name="properties">Optional dictionary of key/value pairs to record.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Warning"/> severity.</para>
        /// <para>Use this method to log system warnings, such as low performance, disk space etc.</para>
        /// <para>Recommendation: messages of this type should be logged in event log and application's errorlog file.</para>
        /// </remarks>
        public static void AppWarning(string warningName, string warningDetails, IDictionary<string, object> properties)
        {
            LogEntry entry = new LogEntry();
            entry.Severity = TraceEventType.Warning;
            entry.Categories.Add("AppWarning");
            entry.Priority = highPriority;

            entry.Title = (warningName != null) ? warningName : "";
            entry.Message = (warningDetails != null) ? warningDetails : "";
            if (properties != null)
                entry.ExtendedProperties = properties;

            WriteLogEntry(entry);
        }

        #endregion

        #region AppInfo.

        /// <summary>
        /// Logs high priority, informational message that indicates a valid application state.
        /// </summary>
        /// <param name="infoTitle">Message title.</param>
        /// <param name="infoDetails">Detailed message body to log.</param>
        /// <param name="properties">Optional dictionary of key/value pairs to record.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Information"/> severity.</para>
        /// <para>Use this method to log confirmational messages, such as when a system is succesfully started or disposed.</para>
        /// <para>Recommendation: messages of this type should be logged in event log and application's errorlog file.</para>
        /// </remarks>
        public static void AppInfo(string infoTitle, string infoDetails, IDictionary<string, object> properties)
        {
            LogEntry entry = new LogEntry();
            entry.Severity = TraceEventType.Information;
            entry.Categories.Add("AppInfo");
            entry.Priority = highPriority;

            entry.Title = (infoTitle != null) ? infoTitle : "";
            entry.Message = (infoDetails != null) ? infoDetails : "";
            if (properties != null)
                entry.ExtendedProperties = properties;

            WriteLogEntry(entry);
        }

        #endregion

        // Transaction log, user events/activity tracing.

        #region Transaction.

        /// <summary>
        /// Logs low priority, informational message that contains operation/transaction details.
        /// </summary>
        /// <param name="tranName">Transaction name/identifier. Typically the name of the method or procedure which handles the transaction.</param>
        /// <param name="actor">Actor which is executing the transaction.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged transactions or concurrent users is large.</para>
        /// </remarks>
        public static void Transaction(string tranName, IActor actor)
        {
            Transaction(tranName, null, actor, null, null, null);
        }

        /// <summary>
        /// Logs low priority, informational message that contains operation/transaction details.
        /// </summary>
        /// <param name="tranName">Transaction name/identifier. Typically the name of the method or procedure which handles the transaction.</param>
        /// <param name="tranDescription">Short, user-friendly description of the operation/transaction.</param>
        /// <param name="actor">Actor which is executing the transaction.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged transactions or concurrent users is large.</para>
        /// </remarks>
        public static void Transaction(string tranName, string tranDescription, IActor actor)
        {
            Transaction(tranName, tranDescription, actor, null, null, null);
        }

        /// <summary>
        /// Logs low priority, informational message that contains operation/transaction details.
        /// </summary>
        /// <param name="tranName">Transaction name/identifier. Typically the name of the method or procedure which handles the transaction.</param>
        /// <param name="tranDescription">Short, user-friendly description of the operation/transaction.</param>
        /// <param name="actor">Actor which is executing the transaction.</param>
        /// <param name="objectType">Type of the object/entity associated with the transaction. Typically table or class name.</param>
        /// <param name="objectId">ID of the object/entity associated with the transaction.</param>
        /// <param name="obj">Optional object whose state/properties are logged.</param>
        /// <param name="objPropertiesToLog">Array of object's public properties to be recorded.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged transactions or concurrent users is large.</para>
        /// </remarks>
        public static void Transaction(string tranName, string tranDescription, IActor actor, string objectType, object objectId, object obj, params string[] objPropertiesToLog)
        {
            Dictionary<string, object> tranDetails = null;
            if (obj != null && objPropertiesToLog.Length > 0)
            {
                tranDetails = new Dictionary<string, object>(objPropertiesToLog.Length);
                foreach (string property in objPropertiesToLog)
                {
                    // Skip duplicate dictionary entries.
                    if (!tranDetails.ContainsKey(property))
                        tranDetails.Add(property, ReflectionUtil.GetPublicPropertyValue(obj, property));
                }
            }

            string idText;
            if (objectId == null)
                idText = null;
            else if (objectId is IEnumerable)
                idText = ArrayUtil.Concat((IEnumerable)objectId, ";");
            else
                idText = objectId.ToString();

            Transaction(tranName, tranDescription, actor, objectType, idText, tranDetails);
        }

        /// <summary>
        /// Logs low priority, informational message that contains operation/transaction details.
        /// </summary>
        /// <param name="tranName">Transaction name/identifier. Typically the name of the method or procedure which handles the transaction.</param>
        /// <param name="tranDescription">Short, user-friendly description of the operation/transaction.</param>
        /// <param name="actor">Actor which is executing the transaction.</param>
        /// <param name="objectType">Type of the object/entity associated with the transaction. Typically table or class name.</param>
        /// <param name="objectId">ID of the object/entity associated with the transaction.</param>
        /// <param name="tranDetails">Optional dictionary of key/value pairs to record.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged transactions or concurrent users is large.</para>
        /// </remarks>
        public static void Transaction(string tranName, string tranDescription, IActor actor, string objectType, object objectId, IDictionary<string, object> tranDetails)
        {
            object actorId = (actor != null) ? actor.Id : null;
            string actorName = (actor != null) ? actor.Name : null;
            LogCategorizedTransaction("Transaction", tranName, tranDescription, actorId, actorName, objectType, objectId, tranDetails);
        }

        private static void LogCategorizedTransaction(string category, string tranName, string tranDescription, object actorId, string actorName, string objectType, object objectId, IDictionary<string, object> tranDetails)
        {
            LogEntry entry = new LogEntry();
            entry.Severity = TraceEventType.Verbose;
            entry.Categories.Add(category);
            entry.Priority = lowPriority;

            entry.Title = (tranName != null) ? tranName : "";
            entry.Message = (tranDescription != null) ? tranDescription : "";

            Dictionary<string, object> extendedProperties = new Dictionary<string, object>();
            if (!string.IsNullOrEmpty(category))
                extendedProperties.Add("Category", category);
            if (actorId != null)
                extendedProperties.Add("ActorID", actorId.ToString());
            if (!string.IsNullOrEmpty(actorName))
                extendedProperties.Add("ActorName", actorName);
            if (!string.IsNullOrEmpty(objectType))
                extendedProperties.Add("ObjectType", objectType);
            if (objectId != null)
                extendedProperties.Add("ObjectID", objectId.ToString());

            if (tranDetails != null)
            {
                foreach (KeyValuePair<string, object> detail in tranDetails)
                {
                    // Skip duplicate dictionary entries.
                    if (!extendedProperties.ContainsKey(detail.Key))
                        extendedProperties.Add(detail.Key, detail.Value);
                }
            }

            if (extendedProperties.Count > 0)
                entry.ExtendedProperties = extendedProperties;

            WriteLogEntry(entry);
        }

        #endregion

        #region UnsuccessfulTran.

        /// <summary>
        /// Logs low priority information about an unsuccesfull/ungranted operation/transaction.
        /// </summary>
        /// <param name="tranName">Transaction name/identifier. Typically the name of the method or procedure which handles the transaction.</param>
        /// <param name="actor">Actor which is executing the transaction.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged transactions or concurrent users is large.</para>
        /// </remarks>
        public static void UnsuccessfulTran(string tranName, IActor actor)
        {
            UnsuccessfulTran(tranName, null, actor, null, null, null);
        }

        /// <summary>
        /// Logs low priority information about an unsuccesfull/ungranted operation/transaction.
        /// </summary>
        /// <param name="tranName">Transaction name/identifier. Typically the name of the method or procedure which handles the transaction.</param>
        /// <param name="tranDescription">Short, user-friendly description of the operation/transaction.</param>
        /// <param name="actor">Actor which is executing the transaction.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged transactions or concurrent users is large.</para>
        /// </remarks>
        public static void UnsuccessfulTran(string tranName, string tranDescription, IActor actor)
        {
            UnsuccessfulTran(tranName, tranDescription, actor, null, null, null);
        }

        /// <summary>
        /// Logs low priority information about an unsuccesfull/ungranted operation/transaction.
        /// </summary>
        /// <param name="tranName">Transaction name/identifier. Typically the name of the method or procedure which handles the transaction.</param>
        /// <param name="tranDescription">Short, user-friendly description of the operation/transaction.</param>
        /// <param name="actor">Actor which is executing the transaction.</param>
        /// <param name="objectType">Type of the object/entity associated with the transaction. Typically table or class name.</param>
        /// <param name="objectId">ID of the object/entity associated with the transaction.</param>
        /// <param name="obj">Optional object whose state/properties are logged.</param>
        /// <param name="objPropertiesToLog">Array of object's public properties to be recorded.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged transactions or concurrent users is large.</para>
        /// </remarks>
        public static void UnsuccessfulTran(string tranName, string tranDescription, IActor actor, string objectType, object objectId, object obj, params string[] objPropertiesToLog)
        {
            Dictionary<string, object> tranDetails = null;
            if (obj != null && objPropertiesToLog.Length > 0)
            {
                tranDetails = new Dictionary<string, object>(objPropertiesToLog.Length);
                foreach (string property in objPropertiesToLog)
                {
                    // Skip duplicate dictionary entries.
                    if (!tranDetails.ContainsKey(property))
                        tranDetails.Add(property, ReflectionUtil.GetPublicPropertyValue(obj, property));
                }
            }

            UnsuccessfulTran(tranName, tranDescription, actor, objectType, objectId, tranDetails);
        }

        /// <summary>
        /// Logs low priority information about an unsuccesfull/ungranted operation/transaction.
        /// </summary>
        /// <param name="tranName">Transaction name/identifier. Typically the name of the method or procedure which handles the transaction.</param>
        /// <param name="tranDescription">Short, user-friendly description of the operation/transaction.</param>
        /// <param name="actor">Actor which is executing the transaction.</param>
        /// <param name="objectType">Type of the object/entity associated with the transaction. Typically table or class name.</param>
        /// <param name="objectId">ID of the object/entity associated with the transaction.</param>
        /// <param name="tranDetails">Optional dictionary of key/value pairs to record.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged transactions or concurrent users is large.</para>
        /// </remarks>
        public static void UnsuccessfulTran(string tranName, string tranDescription, IActor actor, string objectType, object objectId, IDictionary<string, object> tranDetails)
        {
            object actorId = (actor != null) ? actor.Id : null;
            string actorName = (actor != null) ? actor.Name : null;
            LogCategorizedTransaction("UnsuccessfulTran", tranName, tranDescription, actorId, actorName, objectType, objectId, tranDetails);
        }

        #endregion

        #region Activity.

        /// <summary>
        /// Logs low priority, verbose message that traces user activities and events.
        /// </summary>
        /// <param name="evtName">Activity name/identifier. Eg. name of the form and button which is clicked.</param>
        /// <param name="actor">Actor which is initiating the event. Nullable.</param>        
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged events or concurrent users is large.</para>
        /// <para>Use this method to trace user activities in the system such as form navigation and usege.
        /// This data may help when trying to determine what caused a <b>Transaction</b>, <b>AppError</b>
        /// etc. It may also help you to analyze and learn user's habits and improve the ergonomy of an application.</para>
        /// </remarks>
        public static void Activity(string evtName, IActor actor)
        {
            Activity(evtName, null, actor, null);
        }

        /// <summary>
        /// Logs low priority, verbose message that traces user activities and events.
        /// </summary>
        /// <param name="evtName">Activity name/identifier. Eg. name of the form and button which is clicked.</param>
        /// <param name="evtDescription">Short, user-friendly description of the activity.</param>
        /// <param name="actor">Actor which is initiating the event. Nullable.</param>        
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged events or concurrent users is large.</para>
        /// <para>Use this method to trace user activities in the system such as form navigation and usege.
        /// This data may help when trying to determine what caused a <b>Transaction</b>, <b>AppError</b>
        /// etc. It may also help you to analyze and learn user's habits and improve the ergonomy of an application.</para>
        /// </remarks>
        public static void Activity(string evtName, string evtDescription, IActor actor)
        {
            Activity(evtName, evtDescription, actor, null);
        }

        /// <summary>
        /// Logs low priority, verbose message that traces user activities and events.
        /// </summary>
        /// <param name="evtName">Activity name/identifier. Eg. name of the form and button which is clicked.</param>
        /// <param name="evtDescription">Short, user-friendly description of the activity.</param>
        /// <param name="actor">Actor which is initiating the event. Nullable.</param>        
        /// <param name="evtDetails">Optional dictionary of key/value pairs to record.</param>
        /// <remarks><para>Creates a LogEntry with <see cref="TraceEventType.Verbose"/> severity.</para>
        /// <para>Recommendation: messages of this type should be logged in application's trace file or database.
        /// Avoid logging to trace file if the number of logged events or concurrent users is large.</para>
        /// <para>Use this method to trace user activities in the system such as form navigation and usege.
        /// This data may help when trying to determine what caused a <b>Transaction</b>, <b>AppError</b>
        /// etc. It may also help you to analyze and learn user's habits and improve the ergonomy of an application.</para>
        /// </remarks>
        public static void Activity(string evtName, string evtDescription, IActor actor, IDictionary<string, object> evtDetails)
        {
            object actorId = (actor != null) ? actor.Id : null;
            string actorName = (actor != null) ? actor.Name : null;
            LogCategorizedTransaction("Activity", evtName, evtDescription, actorId, actorName, null, null, evtDetails);
        }

        #endregion
    }
}